Introduction

Pour cette deuxième séance, nous allons apprendre à utiliser un planifateur d’itinéraire sur R afin de calculer des isochrones autour de chaque supermarché et ainsi pouvoir identifier les zones de chalandises.

1. Cartographier des isodistances

Une première information simple à obtenir mais très informative peut être obtenue en traçant des courbes d’isodistances autour de chaque supermarchés. On peut ainsi indentifier les zones non-deserviés.

télécharger le package en .zip en cliquant sur ce lien
https://cran.r-project.org/src/contrib/Archive/opentripplanner/opentripplanner_0.4.0.tar.gz

Et installer Java 8 sur votre ordinateur:
https://www.java.com/fr/download/
setwd("C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/")
Avis : The working directory was changed to C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
pacman::p_load(dplyr, ggplot2, sf,readr,readxl,tmap,tmaptools)

#installer le package opentripplaner (en modifiant le chemin d'acces)
install.packages("C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/opentripplanner_0.4.0.tar.gz", repos = NULL, type = "source")
WARNING: Rtools is required to build R packages but is not currently installed. Please download and install the appropriate version of Rtools before proceeding:

https://cran.rstudio.com/bin/windows/Rtools/
Warning in install.packages :
  le package ‘opentripplanner’ est en cours d'utilisation et ne sera pas installé
library(opentripplanner)
iris_grenoble<-st_read("iris_grenoble.gpkg")
Reading layer `iris_grenoble' from data source `C:\Users\boufarsi\Documents\thèse\Enseignement\Geomarketing\New\iris_grenoble.gpkg' using driver `GPKG'
Simple feature collection with 70 features and 36 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 5.678578 ymin: 45.15415 xmax: 5.753078 ymax: 45.21432
Geodetic CRS:  WGS 84
#vérifiez bien que vous avez la derniere version du fichier, avec 22 lignes (22 points de vente)
sirene_grenoble<-st_read("sirene_grenoble.gpkg")
Reading layer `sirene_grenoble' from data source `C:\Users\boufarsi\Documents\thèse\Enseignement\Geomarketing\New\sirene_grenoble.gpkg' using driver `GPKG'
Simple feature collection with 21 features and 106 fields
Geometry type: POINT
Dimension:     XY
Bounding box:  xmin: 5.70661 ymin: 45.16637 xmax: 5.744077 ymax: 45.20106
Geodetic CRS:  WGS 84
st_crs(sirene_grenoble) #l'unité est le mètre
Coordinate Reference System:
  User input: WGS 84 
  wkt:
GEOGCRS["WGS 84",
    ENSEMBLE["World Geodetic System 1984 ensemble",
        MEMBER["World Geodetic System 1984 (Transit)"],
        MEMBER["World Geodetic System 1984 (G730)"],
        MEMBER["World Geodetic System 1984 (G873)"],
        MEMBER["World Geodetic System 1984 (G1150)"],
        MEMBER["World Geodetic System 1984 (G1674)"],
        MEMBER["World Geodetic System 1984 (G1762)"],
        MEMBER["World Geodetic System 1984 (G2139)"],
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]],
        ENSEMBLEACCURACY[2.0]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    USAGE[
        SCOPE["Horizontal component of 3D system."],
        AREA["World."],
        BBOX[-90,-180,90,180]],
    ID["EPSG",4326]]
buffer<-st_buffer(sirene_grenoble,500) #200m isodistance

tmap_mode("view")
tm_shape(iris_grenoble) +tm_text(text = "iris_name",size=0.8)+
  tm_polygons("Pop_15_64", alpha=0.5,
              style="pretty", id="iris_name",
              title="Population (15-64 ans)")+
     tm_shape(buffer)+tm_borders("chartreuse3",lwd=3)+
  tm_shape(sirene_grenoble)+
  tm_dots("enseigne1et",legend.show = TRUE,title="Enseigne",id="enseigne1et",popup.vars="trancheeffe.2",alpha=0.3)+
  tm_layout(legend.outside = TRUE)
tm_shape(iris_grenoble) +tm_text(text = "iris_name",size=0.8)+
  tm_polygons("Pop_15_64", alpha=0.5,
              style="pretty", id="iris_name",
              title="Population (15-64 ans)")+
     tm_shape(buffer)+tm_polygons("enseigne1et",legend.show = FALSE,id="enseigne1et",alpha=0.5)+
   tm_shape(sirene_grenoble)+
  tm_dots("enseigne1et",legend.show = TRUE,title="Enseigne",id="enseigne1et",popup.vars="trancheeffe.2",alpha=0.8)+
  tm_layout(legend.outside = TRUE)
NA

2. Obtenir des isochrones

2.1 Mise en place du calculateur d’itinéraire

  • Nous allons utiliser OpenTripPlanner, un logiciel codé en Java et utilisé notamment dans le calculateur d’itinéraire du TAG. Pour fonctionner, OTP a besoin de 3 choses:

Un dossier & un sous-dossier specifiques à créer dans votre R working directory > OTP > graphs > default

Des données sur le réseau de transport GTFS (General Transit Feed Specification) à déposer dans OTP > graphs > default

Un fond de carte openstreetmap à déposer dans OTP > graphs > default

  • données GTFS
knitr::include_graphics("C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/gtfs.png")

Vous pouvez par exemple trouver les données GTFS sur le site M mobilité du TAG: data.mobilites-m.fr
Une fois téléchargé le fichier SEM-GTFS.zip renommez le en gtfs.zip et placez le dans OTP > graphs > default
  • fond de carte OSM
Il y a aussi beaucoup de sources, par exemple :
https://download.geofabrik.de/europe/france/rhone-alpes.html
Placez le fichier dans OTP > graphs > default


  • Mise en place d’OTP
path_data <- "C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/OTP" #tell him the OTP directory 
path_otp <- otp_dl_jar() #java 
#Vous n'avez besoin de lancer cette ligne qu'une fois
#log <- otp_build_graph(otp = path_otp, # Build Graph
 #                      dir = path_data,memory=15000)
log1<-otp_setup(otp = path_otp, dir = path_data) # Start OTP
otpcon <- otp_connect()
setwd("C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/")
Avis : The working directory was changed to C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
knitr::include_graphics("C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/OTP_capture.png")

2.2 Estimer les isochrones (otp_isochrone())

iso<-otp_isochrone(otpcon,fromPlace = sirene_grenoble,fromID=sirene_grenoble$siret,mode = c("WALK","TRANSIT"),
            maxWalkDistance = 2000,date_time=as.POSIXct(strptime("2022-02-22 08:35", "%Y-%m-%d %H:%M")),
            cutoffSec = c(5, 7,9,11) * 60 ) # Cut offs in seconds
iso$minutes = iso$time / 60

#verification
colnames(iso)[3]<-"siret"
iso<-merge(iso,st_drop_geometry(sirene_grenoble[,c(3,43)]),by="siret",all.y=TRUE)%>%unique() #pour avoir les enseignes mais on a pas d'isochrone pour deux points de vente
sirene_grenoble[16,]$geom[[1]]<-st_point(c(5.728253270386639,45.17659752397455), dim = "XYZ") #ce point ne marchait pas pour 7min

#On recommence
iso<-otp_isochrone(otpcon,fromPlace = sirene_grenoble,fromID=sirene_grenoble$siret,mode = c("WALK","TRANSIT"),
                   maxWalkDistance = 1500,date_time=as.POSIXct(strptime("2022-02-22 08:35", "%Y-%m-%d %H:%M")),
                   cutoffSec = c(5, 7,9,11) * 60 ) # Cut offs in seconds
colnames(iso)[3]<-"siret"

iso<-merge(iso,st_drop_geometry(sirene_grenoble[,c(3,43)]),by="siret",all.y=TRUE)%>%unique() #pour avoir les enseignes mais on a pas d'isochrone pour deux points de vente
iso$minutes = iso$time / 60

3. Cartes finales et indices

3.1 Carte intéractive avec isochrones

tmap_mode("view")
tm_shape(iris_grenoble) +#tm_text(text = "iris_name",size=0.8)+
  tm_polygons("Pop_15_64", alpha=0.5,
              style="pretty", id="iris_name",
              title="Population (15-64 ans)")+
   tm_shape(sirene_grenoble) + 
  tm_dots("enseigne1et",legend.show = TRUE,title="Enseigne",size=0.1,id="enseigne1et",popup.vars="trancheeffe.2")+
  tm_layout(legend.outside = TRUE)+
  tm_shape(iso) +  
  tm_fill("minutes",
          breaks = c(0, 5.01,  7.01,9.01,11.01), title="Isochrones (minutes)",
          style = "fixed",labels =c("0 to 5", "5 to 7", "7 to 9","9 to 11"),
          palette ="-BuPu",id="minutes",alpha = 0.3) +
  tm_borders()

3.2 Analyse de la concurrence

  • Calculez le nombre de supermarchés accessibles par IRIS (st_intersects())
iso420<-iso%>%filter(time==420) #on selectionne seulement l'isocrhone à 7min
#on compte (rowsums) le nombre de supermarchés présent par IRIS(st_intersect)
iris_grenoble$nb_supermarche_per_iris<-rowSums(ifelse(st_intersects(x=iris_grenoble,y=iso420,sparse=F),1,0)) 
tm_shape(iris_grenoble)+tm_polygons("nb_supermarche_per_iris",
              style="pretty", id="iris_name",labels =c("0", "1","2","3","4","5"),
              title="Nb de Supermarchés accessible en 7min")+tm_text(text = "iris_name",size=0.8)
  • Bonus pour la 3ème session, on calcule le nombre de supermarchés de chaque enseigne accessibles par IRIS
data_temp<-matrix(0,nrow=nrow(iris_grenoble),ncol=nrow(iso420))
for(j in 1:nrow(iso420)){
  for (i in 1:nrow(iris_grenoble)) {
    data_temp[i,j]<-as.character(st_intersects(iris_grenoble, iso420,sparse=F)[i,j])
    data_temp[i,j]<-ifelse(data_temp[i,j]=="TRUE",iso420$enseigne1et[j],0)
  }
} #cela nous donne les supermarchés accessibles dans chaque IRIS

data_temp2<-matrix(0,nrow=nrow(data_temp),ncol=length(unique(iso420$enseigne1et)))
for(j in 1:length(unique(iso420$enseigne1et))){
  data_temp2[,j]<-apply(data_temp, 1, function(x) length(which(x==unique(iso420$enseigne1et)[j])))
} #regroupe le dataframe différente avec le nombre de supermarché de chaque enseigne par IRIS

data_temp2<-as.data.frame(data_temp2)
colnames(data_temp2)<-unique(iso420$enseigne1et)
#pensez bien à changer le repertoire ici
write.csv(data_temp2,"C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/nb_pdv_iris.csv")
  • Calculez la surface cumulée couverte par un supermarché pour chaque iris (st_intersection(), st_area())
#Surface cumulée couverte par un supermarché par IRIS

intersect_pct <- st_intersection(iris_grenoble, iso420) %>% #calcul de l'intersection entre les iris et les iso
     mutate(intersect_area = st_area(.)) %>%   # création d'une variable de la surface en intersection
    dplyr::select(iris_name, intersect_area) %>%   # garder que les colonnes utiles
  group_by(iris_name)%>%mutate(intersect_area=sum(intersect_area))%>% #somme de la surface en intersection avec tous les supermarchés par iris
   st_drop_geometry()%>%unique() #On a pas besoin des informations géographiques
Avis : attribute variables are assumed to be spatially constant throughout all geometries
  • Puis calculez la surface de chaque IRIS, le taux de couverture par iris et faîtes une carte
#Surface total des IRIS
iris_grenoble <- mutate(iris_grenoble, iris_area = st_area(iris_grenoble)) #obtenir la surface de chaque iris

#Fusionner les deux 
iris_grenoble <- merge(iris_grenoble, intersect_pct, by = "iris_name", all.x = TRUE) #merge, which introduce some NAs when intersect_area=0

#Calcul du taux de couverture
iris_grenoble <- iris_grenoble %>% mutate(intersect_area=ifelse(is.na(intersect_area),0,intersect_area))%>% 
   mutate(couverture = as.numeric(intersect_area/iris_area)*100) #on obtient le taux de couverture

#Et enfin une carte
tm_shape(iris_grenoble)+tm_polygons(col="couverture", alpha=0.5,
              style="cont", id="iris_name",
              title="Couverture (%)")+tm_text(text = "iris_name",size=0.8)+
   tm_shape(sirene_grenoble) + 
  tm_dots("enseigne1et",legend.show = TRUE,title="Enseigne",size=0.1,id="enseigne1et",popup.vars="trancheeffe.2")+
  tm_layout(legend.outside = TRUE)
NA
  • Enregistrer iris_grenoble pour la prochaine séance
st_write(iris_grenoble,"C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/iris_grenoble2.gpkg")
Layer iris_grenoble2 in dataset C:/Users/boufarsi/Documents/thèse/Enseignement/Geomarketing/New/iris_grenoble2.gpkg already exists:
use either append=TRUE to append to layer or append=FALSE to overwrite layer
Error: Dataset already exists.

Devoirs pour la prochaine séance

Préparez plusieurs cartes avec des isochrones différentes et d’autres variables au niveau des IRIS (carte 3.1) afin de proposer une localisation potentielle d’un nouveau supermarché à Grenoble. Proposez aussi une autre carte (3.2) construite à partir d’une isochrone différente (>7min). Ce travail sera évalué.

LS0tDQp0aXRsZTogIkdlb21hcmtldGluZyBhdmVjIFIgLSBTZXNzaW9uIDIgLSBCb3VmYXJzaSBBZGlsIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQpgYGB7Y3NzLCBlY2hvPUZBTFNFfQ0KLnNwb2lsZXIgew0KICB2aXNpYmlsaXR5OiBoaWRkZW47DQp9DQoNCi5zcG9pbGVyOjpiZWZvcmUgew0KICB2aXNpYmlsaXR5OiB2aXNpYmxlOw0KICBjb250ZW50OiAiQW5zd2VyIg0KfQ0KDQouc3BvaWxlcjpob3ZlciB7DQogIHZpc2liaWxpdHk6IHZpc2libGU7DQp9DQoNCi5zcG9pbGVyOmhvdmVyOjpiZWZvcmUgew0KICBkaXNwbGF5OiBub25lOw0KfQ0KYGBgDQoNCmBgYHtyIHNldHVwLCBlY2hvPUZBTFNFfQ0Ka25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSBub3JtYWxpemVQYXRoKCJDOi9Vc2Vycy9ib3VmYXJzaS9Eb2N1bWVudHMvdGjDqHNlL0Vuc2VpZ25lbWVudC9HZW9tYXJrZXRpbmcvTmV3LyIpKQ0KYGBgDQpJbnRyb2R1Y3Rpb24gDQo9PT09PQ0KUG91ciBjZXR0ZSBkZXV4acOobWUgc8OpYW5jZSwgbm91cyBhbGxvbnMgYXBwcmVuZHJlIMOgIHV0aWxpc2VyIHVuIHBsYW5pZmF0ZXVyIGQnaXRpbsOpcmFpcmUgc3VyIFIgYWZpbiBkZSBjYWxjdWxlciBkZXMgaXNvY2hyb25lcyBhdXRvdXIgZGUgY2hhcXVlIHN1cGVybWFyY2jDqSBldCBhaW5zaSBwb3V2b2lyIGlkZW50aWZpZXIgbGVzIHpvbmVzIGRlIGNoYWxhbmRpc2VzLg0KDQoxLiBDYXJ0b2dyYXBoaWVyIGRlcyBpc29kaXN0YW5jZXMNCj09PT09PQ0KDQpVbmUgcHJlbWnDqHJlIGluZm9ybWF0aW9uIHNpbXBsZSDDoCBvYnRlbmlyIG1haXMgdHLDqHMgaW5mb3JtYXRpdmUgcGV1dCDDqnRyZSBvYnRlbnVlIGVuIHRyYcOnYW50IGRlcyBjb3VyYmVzIGQnaXNvZGlzdGFuY2VzIGF1dG91ciBkZSBjaGFxdWUgc3VwZXJtYXJjaMOpcy4NCk9uIHBldXQgYWluc2kgaW5kZW50aWZpZXIgbGVzIHpvbmVzIG5vbi1kZXNlcnZpw6lzLg0KDQorIFJlcHLDqXNlbnRleiBsZXMgY291cmJlcyBkJ2lzb2Rpc3RhbmNlcyAoc3RfYnVmZmVyKCksIHRtX2JvcmRlcnMoKSkNCmBgYA0KdMOpbMOpY2hhcmdlciBsZSBwYWNrYWdlIGVuIC56aXAgZW4gY2xpcXVhbnQgc3VyIGNlIGxpZW4NCmh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3NyYy9jb250cmliL0FyY2hpdmUvb3BlbnRyaXBwbGFubmVyL29wZW50cmlwcGxhbm5lcl8wLjQuMC50YXIuZ3oNCg0KRXQgaW5zdGFsbGVyIEphdmEgOCBzdXIgdm90cmUgb3JkaW5hdGV1cjoNCmh0dHBzOi8vd3d3LmphdmEuY29tL2ZyL2Rvd25sb2FkLw0KYGBgDQoNCjxidXR0b24gY2xhc3M9ImJ0biBidG4tcHJpbWFyeSIgZGF0YS10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLXRhcmdldD0iI0Jsb2NrMCI+IFNob3cvSGlkZSA8L2J1dHRvbj4gIA0KPGRpdiBpZD0iQmxvY2swIiBjbGFzcz0iY29sbGFwc2UiPiANCmBgYHtyLG1lc3NhZ2U9RkFMU0V9DQpzZXR3ZCgiQzovVXNlcnMvYm91ZmFyc2kvRG9jdW1lbnRzL3Row6hzZS9FbnNlaWduZW1lbnQvR2VvbWFya2V0aW5nL05ldy8iKQ0KcGFjbWFuOjpwX2xvYWQoZHBseXIsIGdncGxvdDIsIHNmLHJlYWRyLHJlYWR4bCx0bWFwLHRtYXB0b29scykNCg0KI2luc3RhbGxlciBsZSBwYWNrYWdlIG9wZW50cmlwcGxhbmVyIChlbiBtb2RpZmlhbnQgbGUgY2hlbWluIGQnYWNjZXMpDQppbnN0YWxsLnBhY2thZ2VzKCJDOi9Vc2Vycy9ib3VmYXJzaS9Eb2N1bWVudHMvdGjDqHNlL0Vuc2VpZ25lbWVudC9HZW9tYXJrZXRpbmcvTmV3L29wZW50cmlwcGxhbm5lcl8wLjQuMC50YXIuZ3oiLCByZXBvcyA9IE5VTEwsIHR5cGUgPSAic291cmNlIikNCmxpYnJhcnkob3BlbnRyaXBwbGFubmVyKQ0KaXJpc19ncmVub2JsZTwtc3RfcmVhZCgiaXJpc19ncmVub2JsZS5ncGtnIikNCiN2w6lyaWZpZXogYmllbiBxdWUgdm91cyBhdmV6IGxhIGRlcm5pZXJlIHZlcnNpb24gZHUgZmljaGllciwgYXZlYyAyMiBsaWduZXMgKDIyIHBvaW50cyBkZSB2ZW50ZSkNCnNpcmVuZV9ncmVub2JsZTwtc3RfcmVhZCgic2lyZW5lX2dyZW5vYmxlLmdwa2ciKQ0KDQpzdF9jcnMoc2lyZW5lX2dyZW5vYmxlKSAjbCd1bml0w6kgZXN0IGxlIG3DqHRyZQ0KYnVmZmVyPC1zdF9idWZmZXIoc2lyZW5lX2dyZW5vYmxlLDUwMCkgIzIwMG0gaXNvZGlzdGFuY2UNCg0KdG1hcF9tb2RlKCJ2aWV3IikNCnRtX3NoYXBlKGlyaXNfZ3Jlbm9ibGUpICt0bV90ZXh0KHRleHQgPSAiaXJpc19uYW1lIixzaXplPTAuOCkrDQogIHRtX3BvbHlnb25zKCJQb3BfMTVfNjQiLCBhbHBoYT0wLjUsDQogICAgICAgICAgICAgIHN0eWxlPSJwcmV0dHkiLCBpZD0iaXJpc19uYW1lIiwNCiAgICAgICAgICAgICAgdGl0bGU9IlBvcHVsYXRpb24gKDE1LTY0IGFucykiKSsNCiAgICAgdG1fc2hhcGUoYnVmZmVyKSt0bV9ib3JkZXJzKCJjaGFydHJldXNlMyIsbHdkPTMpKw0KICB0bV9zaGFwZShzaXJlbmVfZ3Jlbm9ibGUpKw0KICB0bV9kb3RzKCJlbnNlaWduZTFldCIsbGVnZW5kLnNob3cgPSBUUlVFLHRpdGxlPSJFbnNlaWduZSIsaWQ9ImVuc2VpZ25lMWV0Iixwb3B1cC52YXJzPSJ0cmFuY2hlZWZmZS4yIixhbHBoYT0wLjMpKw0KICB0bV9sYXlvdXQobGVnZW5kLm91dHNpZGUgPSBUUlVFKQ0KYGBgDQo8L2Rpdj4NCg0KKyBBdmVjIGRlcyBjZXJjbGVzIHBsZWlucyBwbHV0w7R0IHF1ZSBkZXMgY291cmJlcw0KDQo8YnV0dG9uIGNsYXNzPSJidG4gYnRuLXByaW1hcnkiIGRhdGEtdG9nZ2xlPSJjb2xsYXBzZSIgZGF0YS10YXJnZXQ9IiNCbG9jazAwIj4gU2hvdy9IaWRlIDwvYnV0dG9uPiAgDQo8ZGl2IGlkPSJCbG9jazAwIiBjbGFzcz0iY29sbGFwc2UiPiANCg0KYGBge3J9DQp0bV9zaGFwZShpcmlzX2dyZW5vYmxlKSArdG1fdGV4dCh0ZXh0ID0gImlyaXNfbmFtZSIsc2l6ZT0wLjgpKw0KICB0bV9wb2x5Z29ucygiUG9wXzE1XzY0IiwgYWxwaGE9MC41LA0KICAgICAgICAgICAgICBzdHlsZT0icHJldHR5IiwgaWQ9ImlyaXNfbmFtZSIsDQogICAgICAgICAgICAgIHRpdGxlPSJQb3B1bGF0aW9uICgxNS02NCBhbnMpIikrDQogICAgIHRtX3NoYXBlKGJ1ZmZlcikrdG1fcG9seWdvbnMoImVuc2VpZ25lMWV0IixsZWdlbmQuc2hvdyA9IEZBTFNFLGlkPSJlbnNlaWduZTFldCIsYWxwaGE9MC41KSsNCiAgIHRtX3NoYXBlKHNpcmVuZV9ncmVub2JsZSkrDQogIHRtX2RvdHMoImVuc2VpZ25lMWV0IixsZWdlbmQuc2hvdyA9IFRSVUUsdGl0bGU9IkVuc2VpZ25lIixpZD0iZW5zZWlnbmUxZXQiLHBvcHVwLnZhcnM9InRyYW5jaGVlZmZlLjIiLGFscGhhPTAuOCkrDQogIHRtX2xheW91dChsZWdlbmQub3V0c2lkZSA9IFRSVUUpDQoNCmBgYA0KPC9kaXY+DQoNCjIuIE9idGVuaXIgZGVzIGlzb2Nocm9uZXMNCj09PT09DQoNCjIuMSBNaXNlIGVuIHBsYWNlIGR1IGNhbGN1bGF0ZXVyIGQnaXRpbsOpcmFpcmUNCi0tLS0tLQ0KDQorIE5vdXMgYWxsb25zIHV0aWxpc2VyIE9wZW5UcmlwUGxhbm5lciwgdW4gbG9naWNpZWwgY29kw6kgZW4gSmF2YSBldCB1dGlsaXPDqSBub3RhbW1lbnQgZGFucyBsZSBjYWxjdWxhdGV1ciBkJ2l0aW7DqXJhaXJlIGR1IFRBRy4NClBvdXIgZm9uY3Rpb25uZXIsIE9UUCBhIGJlc29pbiBkZSAzIGNob3NlczoNCg0KVW4gZG9zc2llciAmIHVuIHNvdXMtZG9zc2llciBzcGVjaWZpcXVlcyDDoCBjcsOpZXIgZGFucyB2b3RyZSBSIHdvcmtpbmcgZGlyZWN0b3J5ID4gT1RQID4gZ3JhcGhzID4gZGVmYXVsdA0KDQpEZXMgZG9ubsOpZXMgc3VyIGxlIHLDqXNlYXUgZGUgdHJhbnNwb3J0IEdURlMgKEdlbmVyYWwgVHJhbnNpdCBGZWVkIFNwZWNpZmljYXRpb24pIMOgIGTDqXBvc2VyIGRhbnMgT1RQID4gZ3JhcGhzID4gZGVmYXVsdA0KDQpVbiBmb25kIGRlIGNhcnRlIG9wZW5zdHJlZXRtYXAgw6AgZMOpcG9zZXIgZGFucyBPVFAgPiBncmFwaHMgPiBkZWZhdWx0DQoNCisgZG9ubsOpZXMgR1RGUw0KDQo8YnV0dG9uIGNsYXNzPSJidG4gYnRuLXByaW1hcnkiIGRhdGEtdG9nZ2xlPSJjb2xsYXBzZSIgZGF0YS10YXJnZXQ9IiNCbG9jazEiPiBTaG93L0hpZGUgPC9idXR0b24+ICANCjxkaXYgaWQ9IkJsb2NrMSIgY2xhc3M9ImNvbGxhcHNlIj4gIA0KDQpgYGB7cn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9ib3VmYXJzaS9Eb2N1bWVudHMvdGjDqHNlL0Vuc2VpZ25lbWVudC9HZW9tYXJrZXRpbmcvTmV3L2d0ZnMucG5nIikNCmBgYA0KPC9kaXY+DQoNCjxidXR0b24gY2xhc3M9ImJ0biBidG4tcHJpbWFyeSIgZGF0YS10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLXRhcmdldD0iI0Jsb2NrMiI+IFNob3cvSGlkZSA8L2J1dHRvbj4gIA0KPGRpdiBpZD0iQmxvY2syIiBjbGFzcz0iY29sbGFwc2UiPiAgDQoNCmBgYA0KVm91cyBwb3V2ZXogcGFyIGV4ZW1wbGUgdHJvdXZlciBsZXMgZG9ubsOpZXMgR1RGUyBzdXIgbGUgc2l0ZSBNIG1vYmlsaXTDqSBkdSBUQUc6IGRhdGEubW9iaWxpdGVzLW0uZnINClVuZSBmb2lzIHTDqWzDqWNoYXJnw6kgbGUgZmljaGllciBTRU0tR1RGUy56aXAgcmVub21tZXogbGUgZW4gZ3Rmcy56aXAgZXQgcGxhY2V6IGxlIGRhbnMgT1RQID4gZ3JhcGhzID4gZGVmYXVsdA0KYGBgDQo8L2Rpdj4NCg0KDQorIGZvbmQgZGUgY2FydGUgT1NNDQoNCjxidXR0b24gY2xhc3M9ImJ0biBidG4tcHJpbWFyeSIgZGF0YS10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLXRhcmdldD0iI0Jsb2NrMyI+IFNob3cvSGlkZSA8L2J1dHRvbj4gIA0KPGRpdiBpZD0iQmxvY2szIiBjbGFzcz0iY29sbGFwc2UiPiAgDQpgYGANCklsIHkgYSBhdXNzaSBiZWF1Y291cCBkZSBzb3VyY2VzLCBwYXIgZXhlbXBsZSA6DQpodHRwczovL2Rvd25sb2FkLmdlb2ZhYnJpay5kZS9ldXJvcGUvZnJhbmNlL3Job25lLWFscGVzLmh0bWwNClBsYWNleiBsZSBmaWNoaWVyIGRhbnMgT1RQID4gZ3JhcGhzID4gZGVmYXVsdA0KYGBgDQo8L2Rpdj4NCjxici8+DQoNCisgTWlzZSBlbiBwbGFjZSBkJ09UUA0KYGBge3IscmVzdWx0cz0naGlkZScsbWVzc2FnZT1GQUxTRX0NCnBhdGhfZGF0YSA8LSAiQzovVXNlcnMvYm91ZmFyc2kvRG9jdW1lbnRzL3Row6hzZS9FbnNlaWduZW1lbnQvR2VvbWFya2V0aW5nL05ldy9PVFAiICN0ZWxsIGhpbSB0aGUgT1RQIGRpcmVjdG9yeSANCnBhdGhfb3RwIDwtIG90cF9kbF9qYXIoKSAjamF2YSANCg0KI1ZvdXMgbidhdmV6IGJlc29pbiBkZSBsYW5jZXIgY2V0dGUgbGlnbmUgcXUndW5lIGZvaXMNCiNsb2cgPC0gb3RwX2J1aWxkX2dyYXBoKG90cCA9IHBhdGhfb3RwLCAjIEJ1aWxkIEdyYXBoDQogIyAgICAgICAgICAgICAgICAgICAgICBkaXIgPSBwYXRoX2RhdGEsbWVtb3J5PTE1MDAwKQ0KbG9nMTwtb3RwX3NldHVwKG90cCA9IHBhdGhfb3RwLCBkaXIgPSBwYXRoX2RhdGEpICMgU3RhcnQgT1RQDQpvdHBjb24gPC0gb3RwX2Nvbm5lY3QoKQ0KDQpgYGANCg0KPGJ1dHRvbiBjbGFzcz0iYnRuIGJ0bi1wcmltYXJ5IiBkYXRhLXRvZ2dsZT0iY29sbGFwc2UiIGRhdGEtdGFyZ2V0PSIjQmxvY2szMyI+IFNob3cvSGlkZSA8L2J1dHRvbj4gIA0KPGRpdiBpZD0iQmxvY2szMyIgY2xhc3M9ImNvbGxhcHNlIj4NCg0KYGBge3J9DQpzZXR3ZCgiQzovVXNlcnMvYm91ZmFyc2kvRG9jdW1lbnRzL3Row6hzZS9FbnNlaWduZW1lbnQvR2VvbWFya2V0aW5nL05ldy8iKQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkM6L1VzZXJzL2JvdWZhcnNpL0RvY3VtZW50cy90aMOoc2UvRW5zZWlnbmVtZW50L0dlb21hcmtldGluZy9OZXcvT1RQX2NhcHR1cmUucG5nIikNCmBgYA0KPC9kaXY+DQoNCjIuMiBFc3RpbWVyIGxlcyBpc29jaHJvbmVzIChvdHBfaXNvY2hyb25lKCkpDQotLS0tLS0tDQoNCjxidXR0b24gY2xhc3M9ImJ0biBidG4tcHJpbWFyeSIgZGF0YS10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLXRhcmdldD0iI0Jsb2NrNCI+IFNob3cvSGlkZSA8L2J1dHRvbj4gIA0KPGRpdiBpZD0iQmxvY2s0IiBjbGFzcz0iY29sbGFwc2UiPiAgDQoNCmBgYHtyLHJlc3VsdHM9J2hpZGUnfQ0KaXNvPC1vdHBfaXNvY2hyb25lKG90cGNvbixmcm9tUGxhY2UgPSBzaXJlbmVfZ3Jlbm9ibGUsZnJvbUlEPXNpcmVuZV9ncmVub2JsZSRzaXJldCxtb2RlID0gYygiV0FMSyIsIlRSQU5TSVQiKSwNCiAgICAgICAgICAgIG1heFdhbGtEaXN0YW5jZSA9IDIwMDAsZGF0ZV90aW1lPWFzLlBPU0lYY3Qoc3RycHRpbWUoIjIwMjItMDItMjIgMDg6MzUiLCAiJVktJW0tJWQgJUg6JU0iKSksDQogICAgICAgICAgICBjdXRvZmZTZWMgPSBjKDUsIDcsOSwxMSkgKiA2MCApICMgQ3V0IG9mZnMgaW4gc2Vjb25kcw0KaXNvJG1pbnV0ZXMgPSBpc28kdGltZSAvIDYwDQoNCiN2ZXJpZmljYXRpb24NCmNvbG5hbWVzKGlzbylbM108LSJzaXJldCINCmlzbzwtbWVyZ2UoaXNvLHN0X2Ryb3BfZ2VvbWV0cnkoc2lyZW5lX2dyZW5vYmxlWyxjKDMsNDMpXSksYnk9InNpcmV0IixhbGwueT1UUlVFKSU+JXVuaXF1ZSgpICNwb3VyIGF2b2lyIGxlcyBlbnNlaWduZXMgbWFpcyBvbiBhIHBhcyBkJ2lzb2Nocm9uZSBwb3VyIGRldXggcG9pbnRzIGRlIHZlbnRlDQpzaXJlbmVfZ3Jlbm9ibGVbMTYsXSRnZW9tW1sxXV08LXN0X3BvaW50KGMoNS43MjgyNTMyNzAzODY2MzksNDUuMTc2NTk3NTIzOTc0NTUpLCBkaW0gPSAiWFlaIikgI2NlIHBvaW50IG5lIG1hcmNoYWl0IHBhcyBwb3VyIDdtaW4NCg0KI09uIHJlY29tbWVuY2UNCmlzbzwtb3RwX2lzb2Nocm9uZShvdHBjb24sZnJvbVBsYWNlID0gc2lyZW5lX2dyZW5vYmxlLGZyb21JRD1zaXJlbmVfZ3Jlbm9ibGUkc2lyZXQsbW9kZSA9IGMoIldBTEsiLCJUUkFOU0lUIiksDQogICAgICAgICAgICAgICAgICAgbWF4V2Fsa0Rpc3RhbmNlID0gMTUwMCxkYXRlX3RpbWU9YXMuUE9TSVhjdChzdHJwdGltZSgiMjAyMi0wMi0yMiAwODozNSIsICIlWS0lbS0lZCAlSDolTSIpKSwNCiAgICAgICAgICAgICAgICAgICBjdXRvZmZTZWMgPSBjKDUsIDcsOSwxMSkgKiA2MCApICMgQ3V0IG9mZnMgaW4gc2Vjb25kcw0KY29sbmFtZXMoaXNvKVszXTwtInNpcmV0Ig0KDQppc288LW1lcmdlKGlzbyxzdF9kcm9wX2dlb21ldHJ5KHNpcmVuZV9ncmVub2JsZVssYygzLDQzKV0pLGJ5PSJzaXJldCIsYWxsLnk9VFJVRSklPiV1bmlxdWUoKSAjcG91ciBhdm9pciBsZXMgZW5zZWlnbmVzIG1haXMgb24gYSBwYXMgZCdpc29jaHJvbmUgcG91ciBkZXV4IHBvaW50cyBkZSB2ZW50ZQ0KaXNvJG1pbnV0ZXMgPSBpc28kdGltZSAvIDYwDQpgYGANCjwvZGl2Pg0KDQozLiBDYXJ0ZXMgZmluYWxlcyBldCBpbmRpY2VzDQo9PT09PT0NCg0KMy4xIENhcnRlIGludMOpcmFjdGl2ZSBhdmVjIGlzb2Nocm9uZXMNCi0tLS0tLQ0KDQo8YnV0dG9uIGNsYXNzPSJidG4gYnRuLXByaW1hcnkiIGRhdGEtdG9nZ2xlPSJjb2xsYXBzZSIgZGF0YS10YXJnZXQ9IiNCbG9jazUiPiBTaG93L0hpZGUgPC9idXR0b24+ICANCjxkaXYgaWQ9IkJsb2NrNSIgY2xhc3M9ImNvbGxhcHNlIj4gIA0KDQpgYGB7cixtZXNzYWdlPUZBTFNFfQ0KdG1hcF9tb2RlKCJ2aWV3IikNCnRtX3NoYXBlKGlyaXNfZ3Jlbm9ibGUpICsjdG1fdGV4dCh0ZXh0ID0gImlyaXNfbmFtZSIsc2l6ZT0wLjgpKw0KICB0bV9wb2x5Z29ucygiUG9wXzE1XzY0IiwgYWxwaGE9MC41LA0KICAgICAgICAgICAgICBzdHlsZT0icHJldHR5IiwgaWQ9ImlyaXNfbmFtZSIsDQogICAgICAgICAgICAgIHRpdGxlPSJQb3B1bGF0aW9uICgxNS02NCBhbnMpIikrDQogICB0bV9zaGFwZShzaXJlbmVfZ3Jlbm9ibGUpICsgDQogIHRtX2RvdHMoImVuc2VpZ25lMWV0IixsZWdlbmQuc2hvdyA9IFRSVUUsdGl0bGU9IkVuc2VpZ25lIixzaXplPTAuMSxpZD0iZW5zZWlnbmUxZXQiLHBvcHVwLnZhcnM9InRyYW5jaGVlZmZlLjIiKSsNCiAgdG1fbGF5b3V0KGxlZ2VuZC5vdXRzaWRlID0gVFJVRSkrDQogIHRtX3NoYXBlKGlzbykgKyAgDQogIHRtX2ZpbGwoIm1pbnV0ZXMiLA0KICAgICAgICAgIGJyZWFrcyA9IGMoMCwgNS4wMSwgIDcuMDEsOS4wMSwxMS4wMSksIHRpdGxlPSJJc29jaHJvbmVzIChtaW51dGVzKSIsDQogICAgICAgICAgc3R5bGUgPSAiZml4ZWQiLGxhYmVscyA9YygiMCB0byA1IiwgIjUgdG8gNyIsICI3IHRvIDkiLCI5IHRvIDExIiksDQogICAgICAgICAgcGFsZXR0ZSA9Ii1CdVB1IixpZD0ibWludXRlcyIsYWxwaGEgPSAwLjMpICsNCiAgdG1fYm9yZGVycygpDQpgYGANCjwvZGl2Pg0KDQozLjIgQW5hbHlzZSBkZSBsYSBjb25jdXJyZW5jZSANCi0tLS0tLQ0KKyBDYWxjdWxleiBsZSBub21icmUgZGUgc3VwZXJtYXJjaMOpcyBhY2Nlc3NpYmxlcyBwYXIgSVJJUyAoc3RfaW50ZXJzZWN0cygpKQ0KDQo8YnV0dG9uIGNsYXNzPSJidG4gYnRuLXByaW1hcnkiIGRhdGEtdG9nZ2xlPSJjb2xsYXBzZSIgZGF0YS10YXJnZXQ9IiNCbG9jazYiPiBTaG93L0hpZGUgPC9idXR0b24+ICANCjxkaXYgaWQ9IkJsb2NrNiIgY2xhc3M9ImNvbGxhcHNlIj4gIA0KDQpgYGB7cn0NCmlzbzQyMDwtaXNvJT4lZmlsdGVyKHRpbWU9PTQyMCkgI29uIHNlbGVjdGlvbm5lIHNldWxlbWVudCBsJ2lzb2NyaG9uZSDDoCA3bWluDQojb24gY29tcHRlIChyb3dzdW1zKSBsZSBub21icmUgZGUgc3VwZXJtYXJjaMOpcyBwcsOpc2VudCBwYXIgSVJJUyhzdF9pbnRlcnNlY3QpDQppcmlzX2dyZW5vYmxlJG5iX3N1cGVybWFyY2hlX3Blcl9pcmlzPC1yb3dTdW1zKGlmZWxzZShzdF9pbnRlcnNlY3RzKHg9aXJpc19ncmVub2JsZSx5PWlzbzQyMCxzcGFyc2U9RiksMSwwKSkgDQp0bV9zaGFwZShpcmlzX2dyZW5vYmxlKSt0bV9wb2x5Z29ucygibmJfc3VwZXJtYXJjaGVfcGVyX2lyaXMiLA0KICAgICAgICAgICAgICBzdHlsZT0icHJldHR5IiwgaWQ9ImlyaXNfbmFtZSIsbGFiZWxzID1jKCIwIiwgIjEiLCIyIiwiMyIsIjQiLCI1IiksDQogICAgICAgICAgICAgIHRpdGxlPSJOYiBkZSBTdXBlcm1hcmNow6lzIGFjY2Vzc2libGUgZW4gN21pbiIpK3RtX3RleHQodGV4dCA9ICJpcmlzX25hbWUiLHNpemU9MC44KQ0KYGBgDQo8L2Rpdj4NCg0KKyBCb251cyBwb3VyIGxhIDPDqG1lIHNlc3Npb24sIG9uIGNhbGN1bGUgbGUgbm9tYnJlIGRlIHN1cGVybWFyY2jDqXMgZGUgY2hhcXVlIGVuc2VpZ25lIGFjY2Vzc2libGVzIHBhciBJUklTDQoNCjxidXR0b24gY2xhc3M9ImJ0biBidG4tcHJpbWFyeSIgZGF0YS10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLXRhcmdldD0iI0Jsb2NrNjY2Ij4gU2hvdy9IaWRlIDwvYnV0dG9uPiAgDQo8ZGl2IGlkPSJCbG9jazY2NiIgY2xhc3M9ImNvbGxhcHNlIj4gIA0KDQpgYGB7cn0NCmRhdGFfdGVtcDwtbWF0cml4KDAsbnJvdz1ucm93KGlyaXNfZ3Jlbm9ibGUpLG5jb2w9bnJvdyhpc280MjApKQ0KZm9yKGogaW4gMTpucm93KGlzbzQyMCkpew0KICBmb3IgKGkgaW4gMTpucm93KGlyaXNfZ3Jlbm9ibGUpKSB7DQogICAgZGF0YV90ZW1wW2ksal08LWFzLmNoYXJhY3RlcihzdF9pbnRlcnNlY3RzKGlyaXNfZ3Jlbm9ibGUsIGlzbzQyMCxzcGFyc2U9RilbaSxqXSkNCiAgICBkYXRhX3RlbXBbaSxqXTwtaWZlbHNlKGRhdGFfdGVtcFtpLGpdPT0iVFJVRSIsaXNvNDIwJGVuc2VpZ25lMWV0W2pdLDApDQogIH0NCn0gI2NlbGEgbm91cyBkb25uZSBsZXMgc3VwZXJtYXJjaMOpcyBhY2Nlc3NpYmxlcyBkYW5zIGNoYXF1ZSBJUklTDQoNCmRhdGFfdGVtcDI8LW1hdHJpeCgwLG5yb3c9bnJvdyhkYXRhX3RlbXApLG5jb2w9bGVuZ3RoKHVuaXF1ZShpc280MjAkZW5zZWlnbmUxZXQpKSkNCmZvcihqIGluIDE6bGVuZ3RoKHVuaXF1ZShpc280MjAkZW5zZWlnbmUxZXQpKSl7DQogIGRhdGFfdGVtcDJbLGpdPC1hcHBseShkYXRhX3RlbXAsIDEsIGZ1bmN0aW9uKHgpIGxlbmd0aCh3aGljaCh4PT11bmlxdWUoaXNvNDIwJGVuc2VpZ25lMWV0KVtqXSkpKQ0KfSAjcmVncm91cGUgbGUgZGF0YWZyYW1lIGRpZmbDqXJlbnRlIGF2ZWMgbGUgbm9tYnJlIGRlIHN1cGVybWFyY2jDqSBkZSBjaGFxdWUgZW5zZWlnbmUgcGFyIElSSVMNCg0KZGF0YV90ZW1wMjwtYXMuZGF0YS5mcmFtZShkYXRhX3RlbXAyKQ0KY29sbmFtZXMoZGF0YV90ZW1wMik8LXVuaXF1ZShpc280MjAkZW5zZWlnbmUxZXQpDQojcGVuc2V6IGJpZW4gw6AgY2hhbmdlciBsZSByZXBlcnRvaXJlIGljaQ0Kd3JpdGUuY3N2KGRhdGFfdGVtcDIsIkM6L1VzZXJzL2JvdWZhcnNpL0RvY3VtZW50cy90aMOoc2UvRW5zZWlnbmVtZW50L0dlb21hcmtldGluZy9OZXcvbmJfcGR2X2lyaXMuY3N2IikNCmBgYA0KPC9kaXY+DQoNCisgQ2FsY3VsZXogbGEgc3VyZmFjZSBjdW11bMOpZSBjb3V2ZXJ0ZSBwYXIgdW4gc3VwZXJtYXJjaMOpIHBvdXIgY2hhcXVlIGlyaXMgKHN0X2ludGVyc2VjdGlvbigpLCBzdF9hcmVhKCkpDQoNCjxidXR0b24gY2xhc3M9ImJ0biBidG4tcHJpbWFyeSIgZGF0YS10b2dnbGU9ImNvbGxhcHNlIiBkYXRhLXRhcmdldD0iI0Jsb2NrNyI+IFNob3cvSGlkZSA8L2J1dHRvbj4gIA0KPGRpdiBpZD0iQmxvY2s3IiBjbGFzcz0iY29sbGFwc2UiPiANCg0KYGBge3J9DQojU3VyZmFjZSBjdW11bMOpZSBjb3V2ZXJ0ZSBwYXIgdW4gc3VwZXJtYXJjaMOpIHBhciBJUklTDQoNCmludGVyc2VjdF9wY3QgPC0gc3RfaW50ZXJzZWN0aW9uKGlyaXNfZ3Jlbm9ibGUsIGlzbzQyMCkgJT4lICNjYWxjdWwgZGUgbCdpbnRlcnNlY3Rpb24gZW50cmUgbGVzIGlyaXMgZXQgbGVzIGlzbw0KICAgICBtdXRhdGUoaW50ZXJzZWN0X2FyZWEgPSBzdF9hcmVhKC4pKSAlPiUgICAjIGNyw6lhdGlvbiBkJ3VuZSB2YXJpYWJsZSBkZSBsYSBzdXJmYWNlIGVuIGludGVyc2VjdGlvbg0KICAgIGRwbHlyOjpzZWxlY3QoaXJpc19uYW1lLCBpbnRlcnNlY3RfYXJlYSkgJT4lICAgIyBnYXJkZXIgcXVlIGxlcyBjb2xvbm5lcyB1dGlsZXMNCiAgZ3JvdXBfYnkoaXJpc19uYW1lKSU+JW11dGF0ZShpbnRlcnNlY3RfYXJlYT1zdW0oaW50ZXJzZWN0X2FyZWEpKSU+JSAjc29tbWUgZGUgbGEgc3VyZmFjZSBlbiBpbnRlcnNlY3Rpb24gYXZlYyB0b3VzIGxlcyBzdXBlcm1hcmNow6lzIHBhciBpcmlzDQogICBzdF9kcm9wX2dlb21ldHJ5KCklPiV1bmlxdWUoKSAjT24gYSBwYXMgYmVzb2luIGRlcyBpbmZvcm1hdGlvbnMgZ8Opb2dyYXBoaXF1ZXMNCg0KYGBgDQo8L2Rpdj4NCg0KKyBQdWlzIGNhbGN1bGV6IGxhIHN1cmZhY2UgZGUgY2hhcXVlIElSSVMsIGxlIHRhdXggZGUgY291dmVydHVyZSBwYXIgaXJpcyBldCBmYcOudGVzIHVuZSBjYXJ0ZQ0KDQo8YnV0dG9uIGNsYXNzPSJidG4gYnRuLXByaW1hcnkiIGRhdGEtdG9nZ2xlPSJjb2xsYXBzZSIgZGF0YS10YXJnZXQ9IiNCbG9jazgiPiBTaG93L0hpZGUgPC9idXR0b24+ICANCjxkaXYgaWQ9IkJsb2NrOCIgY2xhc3M9ImNvbGxhcHNlIj4gDQoNCmBgYHtyfQ0KI1N1cmZhY2UgdG90YWwgZGVzIElSSVMNCmlyaXNfZ3Jlbm9ibGUgPC0gbXV0YXRlKGlyaXNfZ3Jlbm9ibGUsIGlyaXNfYXJlYSA9IHN0X2FyZWEoaXJpc19ncmVub2JsZSkpICNvYnRlbmlyIGxhIHN1cmZhY2UgZGUgY2hhcXVlIGlyaXMNCg0KI0Z1c2lvbm5lciBsZXMgZGV1eCANCmlyaXNfZ3Jlbm9ibGUgPC0gbWVyZ2UoaXJpc19ncmVub2JsZSwgaW50ZXJzZWN0X3BjdCwgYnkgPSAiaXJpc19uYW1lIiwgYWxsLnggPSBUUlVFKSAjbWVyZ2UsIHdoaWNoIGludHJvZHVjZSBzb21lIE5BcyB3aGVuIGludGVyc2VjdF9hcmVhPTANCg0KI0NhbGN1bCBkdSB0YXV4IGRlIGNvdXZlcnR1cmUNCmlyaXNfZ3Jlbm9ibGUgPC0gaXJpc19ncmVub2JsZSAlPiUgbXV0YXRlKGludGVyc2VjdF9hcmVhPWlmZWxzZShpcy5uYShpbnRlcnNlY3RfYXJlYSksMCxpbnRlcnNlY3RfYXJlYSkpJT4lIA0KICAgbXV0YXRlKGNvdXZlcnR1cmUgPSBhcy5udW1lcmljKGludGVyc2VjdF9hcmVhL2lyaXNfYXJlYSkqMTAwKSAjb24gb2J0aWVudCBsZSB0YXV4IGRlIGNvdXZlcnR1cmUNCg0KI0V0IGVuZmluIHVuZSBjYXJ0ZQ0KdG1fc2hhcGUoaXJpc19ncmVub2JsZSkrdG1fcG9seWdvbnMoY29sPSJjb3V2ZXJ0dXJlIiwgYWxwaGE9MC41LA0KICAgICAgICAgICAgICBzdHlsZT0iY29udCIsIGlkPSJpcmlzX25hbWUiLA0KICAgICAgICAgICAgICB0aXRsZT0iQ291dmVydHVyZSAoJSkiKSt0bV90ZXh0KHRleHQgPSAiaXJpc19uYW1lIixzaXplPTAuOCkrDQogICB0bV9zaGFwZShzaXJlbmVfZ3Jlbm9ibGUpICsgDQogIHRtX2RvdHMoImVuc2VpZ25lMWV0IixsZWdlbmQuc2hvdyA9IFRSVUUsdGl0bGU9IkVuc2VpZ25lIixzaXplPTAuMSxpZD0iZW5zZWlnbmUxZXQiLHBvcHVwLnZhcnM9InRyYW5jaGVlZmZlLjIiKSsNCiAgdG1fbGF5b3V0KGxlZ2VuZC5vdXRzaWRlID0gVFJVRSkNCiAgDQpgYGANCjwvZGl2Pg0KDQorIEVucmVnaXN0cmVyIGlyaXNfZ3Jlbm9ibGUgcG91ciBsYSBwcm9jaGFpbmUgc8OpYW5jZQ0KYGBge3J9DQpzdF93cml0ZShpcmlzX2dyZW5vYmxlLCJDOi9Vc2Vycy9ib3VmYXJzaS9Eb2N1bWVudHMvdGjDqHNlL0Vuc2VpZ25lbWVudC9HZW9tYXJrZXRpbmcvTmV3L2lyaXNfZ3Jlbm9ibGUyLmdwa2ciKQ0KDQpgYGANCkRldm9pcnMgcG91ciBsYSBwcm9jaGFpbmUgc8OpYW5jZQ0KPT09PT09PT0NClByw6lwYXJleiBwbHVzaWV1cnMgY2FydGVzIGF2ZWMgZGVzIGlzb2Nocm9uZXMgZGlmZsOpcmVudGVzIGV0IGQnYXV0cmVzIHZhcmlhYmxlcyBhdSBuaXZlYXUgZGVzIElSSVMgKGNhcnRlIDMuMSkgYWZpbiBkZSBwcm9wb3NlciB1bmUgbG9jYWxpc2F0aW9uIHBvdGVudGllbGxlIGQndW4gbm91dmVhdSBzdXBlcm1hcmNow6kgw6AgR3Jlbm9ibGUuIFByb3Bvc2V6IGF1c3NpIHVuZSBhdXRyZSBjYXJ0ZSAoMy4yKSBjb25zdHJ1aXRlIMOgIHBhcnRpciBkJ3VuZSBpc29jaHJvbmUgZGlmZsOpcmVudGUgKD43bWluKS4gQ2UgdHJhdmFpbCBzZXJhIMOpdmFsdcOpLg0K